package org.NooLab.glue.net.ftp;

import java.io.File;
import java.io.IOException;
import java.util.*;

import org.NooLab.glue.net.UserData;
import org.NooLab.utilities.files.DFutils;
import org.NooLab.utilities.logging.* ;
import org.apache.ftpserver.*;
import org.apache.ftpserver.ftplet.*;
import org.apache.ftpserver.listener.*;
import org.apache.ftpserver.ssl.SslConfigurationFactory;
import org.apache.ftpserver.usermanager.*;
import org.apache.ftpserver.usermanager.impl.*;


import java.util.logging.*; 

// import org.apache.ftpserver.filesystem.*;
// import org.apache.ftpserver.filesystem.nativefs.NativeFileSystemFactory;

// http://repo1.maven.org/maven2/org/apache/ftpserver/ftpserver-core/1.0.6/

/*
  
  IMPORTANT:
  
  after construction, call prepare() !!  (optionally, set directories and users before)
  
  construction of server is importing the interface which binds the server 
  to the application
  
  browsing sources
  http://www.jarvana.com/jarvana/archive-details/org/apache/ftpserver/ftplet-api/1.0.6/ftplet-api-1.0.6-sources.jar
  
  http://svn.apache.org/viewvc/mina/ftpserver/branches/branch_1.4/core/src/java/org/apache/ftpserver/
  
 */

/**
 * 
 * 
 */
public class FtpServerApaj {

	private java.util.logging.Logger jlogger ;
	private java.util.logging.Level loglevel ;
 

	int listeningPort = 21 ;
	String ftprootDir = "" ;
	
	String userMgrPropertiesFilename ;
	Map<String, Ftplet> ftpletsMapped = new HashMap<String, Ftplet>() ; 

	FtpServerFactory serverFactory ;
	FtpServer server;
	ListenerFactory listenFactory ;
	PropertiesUserManagerFactory userManagerFactory ;
	UserManager usermgr ;
	ConnectionConfigFactory connectionConfigFactory ;
	ConnectionConfig connectionConfig; 
	FileSystemFactory filesystemFactory ;
	File userMgrFile;
	
	UserData baseBoxUser ;
	
	protected GlueFTPlet ftpletPlug ;	
	GlueFtpletHandlerIntf ftpGlue;
	
	// general

	boolean ftpStreaming = false;
	
	private int serverPrepared=0; 
	private int printLevel = 2 ;
	
 	DFutils fileutil = new DFutils() ;
 	PrintLog out = new PrintLog(printLevel,true);
	 
 	
	public FtpServerApaj( int listeningport , GlueFtpletHandlerIntf ftpglue, UserData basicuser, int loglevel){
		 
		ftpGlue = ftpglue ;
		listeningPort =  listeningport ;
		
		baseBoxUser = basicuser ;
		
		constructCompletion(loglevel) ;
		
		customizeLogging();
	}
		
	public FtpServerApaj( GlueFtpletHandlerIntf ftpglue){
	
		ftpGlue = ftpglue ;
		
		constructCompletion(0) ;
	}
	
	public void prepare(){
		String cfgPath ;
		serverPrepared = 1;
		
		ftprootDir = "C:/temp/noolab/ftp/" ;
		fileutil.createDir(ftprootDir);
		
		cfgPath = "C:/temp/noolab/ftp/config/" ; 
		fileutil.createDir(cfgPath);
		// ensure/create this directory
		
		userMgrPropertiesFilename = cfgPath + "users.props";
		
		userMgrFile = new File( userMgrPropertiesFilename ) ; // "c:/jbossusers.properties") ;
		if (userMgrFile.exists()==false){
			try {
				userMgrFile.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		userManagerFactory = new PropertiesUserManagerFactory();
	    // userManagerFactory.setFile( userMgrFile );
	    userManagerFactory.setPasswordEncryptor(new SaltedPasswordEncryptor());
	    
	    usermgr = userManagerFactory.createUserManager();
	    
	    init();
	}

	public void stop(){
		
		out.print(2, "attempting to stop the ftp server ...") ;
		server.stop() ;
		
		if (server.isStopped()){
			out.print(2, "ftp server has been stopped.") ;
		}
	}
	
	public void start(){
		int port ; 
		
		if (serverPrepared<=0){
			prepare();
		}
		// start the server
		try {
			
			out.print(2, "attempting to start the ftp server ...") ;
			server.start();
			
			port = serverFactory.getListener("default").getPort() ;
			
			out.print(2, "ftp server started, listening on port "+port ) ; 
			
		} catch (FtpException e) {
			 
			e.printStackTrace();
		}
	}

	public void addPort( int port, String listenerName){
		Listener listener;
		
		if (listenerName.length()==0){
			listenerName = "default" ;
		}
	     
		
		listenFactory = new ListenerFactory();
		        
		// set the port of the listener
		listenFactory.setPort(port);
		
		listener = listenFactory.createListener() ;
		
		 	 
		// listenFactory.getServerAddress();
		// replace the default listener
		serverFactory.addListener( listenerName, listener );
	
	}

	public UserManager addUser( File file, final String username, final String password, final String userDir){
		
		String ftpUserPath;
		UserManager usrmgr ;
		BaseUser user ;
		List<Authority> authorities ;
	
		
		// ensure/create this directory : ftproot
		ftpUserPath = ftprootDir + userDir ;
		
		fileutil.createDir(ftpUserPath) ;
		
		
	    usrmgr = userManagerFactory.createUserManager();
	
	    user = new BaseUser();
	    user.setName(username);
	    user.setPassword(password);
	    user.setHomeDirectory( ftpUserPath );
	
	    authorities = new ArrayList<Authority>();
	    authorities.add(new WritePermission());        
	    user.setAuthorities(authorities);
	
	
	    try {
	    	usrmgr.save(user);
	    } catch (FtpException e) {
	        e.printStackTrace();
	    }
	    return usrmgr;
	
	}

	/**
	 * 
	 * ftplet
	 * 
	 * restartMode controls the behavior of the server when adding a ftplet a,
	 * - 0  = no special activity, wait for manual restart
	 * - 1  = immediate restart of server
	 * - 2+ = wait at most n milliseconds before closing the session and sending 
	 *         code 226 (Transfer complete), if possible, the server will initiate
	 *         the restart if there are no connections ()
	 * 
	 * the calling class must implement the interface GlueFtpletHandlerIntf, then the 
	 * data will be sent directly to the implemented methods;
	 * 
	 * 
	 * @param ftpglue
	 * @param restartMode
	 */
	public void addFtpLet( GlueFtpletHandlerIntf ftpglue, int restartMode ){
		
		ftpletsMapped.put("glueInterception", ftpletPlug) ;
		
		if (restartMode==1){
			stop();
			start() ;
			return;
		}
		
		if (restartMode>=2){
			// initialize a timer/thread 
			if (ftpletPlug.openConnectionsCount==0){
				stop();
				start() ;
			}
		}
		
		
	}

	
	public GlueFtpletHandlerIntf getFtpGlue() {
		return ftpGlue;
	}

	public GlueFTPlet getFtpletPlug() {
		return ftpletPlug;
	}

	@SuppressWarnings("static-access")
	private void init(){
		
		// create new basic user (see below for method "addUser()" )
		usermgr = addUser( userMgrFile, "noolab", "glue", "messageboard");
		
		// ----------------------
		
		// allowing for anonymous connections
		// see: http://stackoverflow.com/questions/1808746/how-to-allow-anonymous-login-in-org-apache-ftpserver
		/*
			connectionConfigFactory = new ConnectionConfigFactory();
			connectionConfigFactory.setAnonymousLoginEnabled(false);
			connectionConfig = connectionConfigFactory.createConnectionConfig();
	
			// this class is below
			serverFactory.setUserManager(new CustomUserManagerFactory().createUserManager());
		*/
		
		// ---------------------- 
		
		// filesystemFactory = new NativeFileSystemFactory() ;
		// filesystemFactory.createFileSystemView(user);
		
		// ----------------------
		
		// DefaultFtpServerContext context = new DefaultFtpServerContext();
		serverFactory = new FtpServerFactory();
	
		// Logger logger = serverFactory.getServerContext().getLOG();
		// String loggername= logger.ROOT_LOGGER_NAME;
		
		 
		
		addPort(listeningPort, "default");
		addPort(7200, "B");
		
		// - - - - - - - - - - - - - - - - - - - - - - - -  
		if ((ftpletsMapped!=null) && (ftpletsMapped.size()>0)){
			serverFactory.setFtplets(ftpletsMapped) ;
		}
		
		// - - - - - - - - - - - - - - - - - - - - - - - -
	    // serverFactory.setFileSystem( filesystemFactory) ;
		// - - - - - - - - - - - - - - - - - - - - - - - -
		if (connectionConfig!=null){
			serverFactory.setConnectionConfig( connectionConfig );
		}
		// - - - - - - - - - - - - - - - - - - - - - - - -
		if (usermgr==null){
			return ;
		}
		serverFactory.setUserManager( usermgr );
		// - - - - - - - - - - - - - - - - - - - - - - - -
		
		
		server = serverFactory.createServer();
		
		
		/*
		
		

    	
		
		Logger logger = LoggerFactory.getLogger(org.apache.ftpserver.FtpServer.class);
	    logger.info("Hello World");
	    
	    
	    String str = logger.ROOT_LOGGER_NAME;
	    logger = LoggerFactory.getLogger("ROOT");
	    
	    out.print(2, str);
	    */
	}

	private void constructCompletion(int loglevel){
	
		prepareLogging(loglevel);
		
		ftpletPlug = new GlueFTPlet( ftpGlue, printLevel );
		
		ftpletsMapped.put("glueInterception", ftpletPlug) ;
		
		
	}

	/**
	 * levelAlias reflects the loglevels of the underlying logger into a simple variable
	 * 
	 * @param levelAlias
	 */
	private void prepareLogging( int levelAlias){
		
		jlogger = java.util.logging.Logger.getLogger("");
		
		if (levelAlias==0){
			printLevel = 1;
			loglevel = java.util.logging.Level.OFF ;
		}
		if (levelAlias==1){
			printLevel = 2;
			loglevel = java.util.logging.Level.OFF ;
		}
		if (levelAlias==2){
			printLevel = 2;
			loglevel = java.util.logging.Level.INFO ;
		}
		if (levelAlias>=3){
			printLevel = 4;
			loglevel = java.util.logging.Level.ALL ;
		}
		
		jlogger.setLevel(loglevel) ;
		
		/*
		Level level = Level.OFF;
		
		LogManager logmgr = LogManager.getLogManager();
		
		ConsoleHandler  clog = new ConsoleHandler();
		clog.setLevel( level);
		
		org.slf4j.Logger _logger = LoggerFactory.getLogger("ROOT");
		java.util.logging.Logger jlogger = logmgr.getLogger( _logger.getName() ) ;
		*/
	}

	private void customizeLogging(){
	
		Handler[]  handlers;
		Handler  handler;
		Filter logf;
		Enumeration<String> names ;
	
		/*
		 	remove console logging
		 	Logger rootlogger = Logger.getLogger("");
			rootlogger.removeHandler(rootlogger.getHandlers()[0]);
			
			for (Handler handler : Logger.getLogger("").getHandlers()) {
				handler.setLevel(Level.CONFIG);
			}
	
		 */
		handlers = jlogger.getHandlers();
		
		for (int i=0;i<handlers.length;i++){
			handler = handlers[i] ;
			logf = handler.getFilter() ;
			
		}
		
		LogManager logmgr = LogManager.getLogManager();
		
		names = logmgr.getLoggerNames();
		
		
		
	}

	private void prepareUserManagerStorage(){
		/*
		 	configure() and createDefaultUsers() of the  PropertiesUserManager
		  
		 */
	}
	
	public String getFtprootDir() {
		return ftprootDir;
	}


	public void setFtprootDir(String ftprootDir) {
		this.ftprootDir = ftprootDir;
	}

	public void enableSSL(){
		 
	
		// define SSL configuration
		SslConfigurationFactory ssl = new SslConfigurationFactory();
		ssl.setKeystoreFile(new File("src/test/resources/ftpserver.jks"));
		ssl.setKeystorePassword("password");
	
		// set the SSL configuration for the listener
		listenFactory.setSslConfiguration(ssl.createSslConfiguration());
		listenFactory.setImplicitSsl(true);
	
		// replace the default listener
		serverFactory.addListener("default", listenFactory.createListener());
		        
		userManagerFactory = new PropertiesUserManagerFactory();
		userManagerFactory.setFile( new File("myusers.properties") );
		        
		serverFactory.setUserManager(userManagerFactory.createUserManager());
		        
	}

	public void setPrintLevel(int printLevel) {
		this.printLevel = printLevel;
		out = new PrintLog(printLevel,true);
		ftpletPlug.setPrintLevel(printLevel) ;
	}

	
}

class CustomUserManagerFactory implements UserManagerFactory {

    @Override
    public UserManager createUserManager() {
        return new CustomUserManager("admin", new ClearTextPasswordEncryptor());
    }
}

class CustomUserManager extends AbstractUserManager {
    private BaseUser testUser;
    private BaseUser anonUser;

    String TEST_USERNAME, TEST_PASSWORD;
    
    public CustomUserManager(String adminName, PasswordEncryptor passwordEncryptor) {
        super(adminName, passwordEncryptor);

        testUser = new BaseUser();
        testUser.setAuthorities(Arrays.asList(new Authority[] {new ConcurrentLoginPermission(1, 1)}));
        testUser.setEnabled(true);
        testUser.setHomeDirectory("");
        testUser.setMaxIdleTime(10000);
        testUser.setName("");
        testUser.setPassword("");

        anonUser = new BaseUser(testUser);
        anonUser.setName("anonymous");
    }

    @Override
    public User getUserByName(String username) throws FtpException {
        if(TEST_USERNAME.equals(username)) {
            return testUser;
        } else if(anonUser.getName().equals(username)) {
            return anonUser;
        }

        return null;
    }

    @Override
    public String[] getAllUserNames() throws FtpException {
        return new String[] {TEST_USERNAME, anonUser.getName()};
    }

    @Override
    public void delete(String username) throws FtpException {
        //no opt
    }

   

    @Override
    public boolean doesExist(String username) throws FtpException {
        return (TEST_USERNAME.equals(username) || anonUser.getName().equals(username)) ? true : false;
    }

    @Override
    public User authenticate(Authentication authentication) throws AuthenticationFailedException {
        if(UsernamePasswordAuthentication.class.isAssignableFrom(authentication.getClass())) {
            UsernamePasswordAuthentication upAuth = (UsernamePasswordAuthentication) authentication;

            if(TEST_USERNAME.equals(upAuth.getUsername()) && TEST_PASSWORD.equals(upAuth.getPassword())) {
                return testUser;
            }

            if(anonUser.getName().equals(upAuth.getUsername())) {
                return anonUser;
            }
        } else if(AnonymousAuthentication.class.isAssignableFrom(authentication.getClass())) {
            return anonUser;
        }

        return null;
    }

 
 
	public void save(User arg0) throws FtpException {
		 
		
	}
}

